#include <iostream>
#include <sstream>
#include "Poco/Crypto/CipherFactory.h"
#include "Poco/Crypto/Cipher.h"
#include "Poco/Crypto/CipherKey.h"
#include "Poco/Crypto/CryptoStream.h"
#include "Poco/StreamCopier.h"
#include "BCrypto.h"

using namespace SystemTool;

static std::string arrBlockType[] = {
    "aes-128",
    "aes-256",
    "des",
    "des3"};

static std::string arrCipherMode[] = {
    "stream-cipher",
    "ecb",
    "cbc",
    "cfb",
    "ofb",
    "ctr",
    "gcm",
    "ccm"};

static std::string arrDigestType[] = {
    "md5",
    "sha1"};

// Encrypt data
std::string BCrypto::Encrypt(std::string &strData,
                       std::string strPassPhrase,
                       std::string strSalt,
                       BlockType eBlockType,
                       CipherMode eMode,
                       CipherDigestType eDigestType,
                       CipherResultType eRTType)
{
    if (strData.empty())
    {
        return "";
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::string strDigestType = arrDigestType[eDigestType];
    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           strPassPhrase,
                                                                                                                           strSalt,
                                                                                                                           2000,
                                                                                                                           strDigestType));
    if (pCipher.get() == nullptr)
    {
        return "";
    }

    std::string strOut = pCipher->encryptString(strData, (Poco::Crypto::Cipher::Encoding)eRTType);
    return strOut;
}

// Encrypt data
std::string BCrypto::EncryptByKV(std::string &strData,
                       std::string strKey,
                       std::string strIV,
                       BlockType eBlockType,
                       CipherMode eMode,
                       CipherResultType eRTType)
{
    if (strData.empty())
    {
        return "";
    }

    if (strKey.empty())
    {
        strKey = "wewqerrqwerqwrqffdsfrwerqgdgbsgr";
    }

    if (strIV.empty())
    {
        strIV = "dkfdjgakhfjuwenjkshfiwdplpnmfjqa";
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::vector<unsigned char> vKey;
    for (size_t index = 0; index < strKey.length(); index++)
    {
        vKey.push_back(strKey[index]);
    }
    std::vector<unsigned char> vIV;
    for (size_t index = 0; index < strIV.length(); index++)
    {
        vIV.push_back(strIV[index]);
    }

    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           vKey,
                                                                                                                           vIV));
    if (pCipher.get() == nullptr)
    {
        return "";
    }

    std::string strOut = pCipher->encryptString(strData, (Poco::Crypto::Cipher::Encoding)eRTType);
    return strOut;
}

// Decrypt data
std::string BCrypto::Decrypt(std::string &strData,
                       std::string strPassPhrase,
                       std::string strSalt,
                       BlockType eBlockType,
                       CipherMode eMode,
                       CipherDigestType eDigestType,
                       CipherResultType eRTType)
{
    if (strData.empty())
    {
        return "";
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::string strDigestType = arrDigestType[eDigestType];
    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           strPassPhrase,
                                                                                                                           strSalt,
                                                                                                                           2000,
                                                                                                                           strDigestType));
    if (pCipher.get() == nullptr)
    {
        return "";
    }

    std::string strOut = pCipher->decryptString(strData, (Poco::Crypto::Cipher::Encoding)eRTType);
    return strOut;
}

// Decrypt data
std::string BCrypto::DecryptByKV(std::string &strData,
                       std::string strKey,
                       std::string strIV,
                       BlockType eBlockType,
                       CipherMode eMode,
                       CipherResultType eRTType)
{
    if (strData.empty())
    {
        return "";
    }

    if (strKey.empty())
    {
        strKey = "wewqerrqwerqwrqffdsfrwerqgdgbsgr";
    }

    if (strIV.empty())
    {
        strIV = "dkfdjgakhfjuwenjkshfiwdplpnmfjqa";
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::vector<unsigned char> vKey;
    for (size_t index = 0; index < strKey.length(); index++)
    {
        vKey.push_back(strKey[index]);
    }
    std::vector<unsigned char> vIV;
    for (size_t index = 0; index < strIV.length(); index++)
    {
        vIV.push_back(strIV[index]);
    }

    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           vKey,
                                                                                                                           vIV));
    if (pCipher.get() == nullptr)
    {
        return "";
    }

    std::string strOut = pCipher->decryptString(strData, (Poco::Crypto::Cipher::Encoding)eRTType);
    return strOut;
}

// Encryp the stream
void BCrypto::EncryptStream(std::istream &is,
                     std::ostream &os,
                     std::string strPassPhrase,
                     std::string strSalt,
                     BlockType eBlockType,
                     CipherMode eMode,
                     CipherDigestType eDigestType,
                     CipherResultType eRTType)
{
    if (!is.good())
    {
        return;
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::string strDigestType = arrDigestType[eDigestType];
    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           strPassPhrase,
                                                                                                                           strSalt,
                                                                                                                           2000,
                                                                                                                           strDigestType));
    if (pCipher.get() == nullptr)
    {
        return;
    }

    Poco::Crypto::CryptoTransform::Ptr pEncryptor = pCipher->createEncryptor();
    Poco::Crypto::CryptoOutputStream encryptorStream(os, pEncryptor);
    Poco::StreamCopier::copyStream(is, encryptorStream);
    encryptorStream.close();
}

// Encryp the stream
void BCrypto::EncryptStreamByKV(std::istream &is,
                     std::ostream &os,
                     std::string strKey,
                     std::string strIV,
                     BlockType eBlockType,
                     CipherMode eMode,
                     CipherResultType eRTType)
{
    if (!is.good())
    {
        return;
    }

    if (strKey.empty())
    {
        strKey = "wewqerrqwerqwrqffdsfrwerqgdgbsgr";
    }

    if (strIV.empty())
    {
        strIV = "dkfdjgakhfjuwenjkshfiwdplpnmfjqa";
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::vector<unsigned char> vKey;
    for (size_t index = 0; index < strKey.length(); index++)
    {
        vKey.push_back(strKey[index]);
    }
    std::vector<unsigned char> vIV;
    for (size_t index = 0; index < strIV.length(); index++)
    {
        vIV.push_back(strIV[index]);
    }

    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           vKey,
                                                                                                                           vIV));
    if (pCipher.get() == nullptr)
    {
        return;
    }

    Poco::Crypto::CryptoTransform::Ptr pEncryptor = pCipher->createEncryptor();
    Poco::Crypto::CryptoOutputStream encryptorStream(os, pEncryptor);
    Poco::StreamCopier::copyStream(is, encryptorStream);
    encryptorStream.close();
}

// Decrypt the stream
void BCrypto::Decrypt(std::istream &is,
                     std::ostream &os,
                     std::string strPassPhrase,
                     std::string strSalt,
                     BlockType eBlockType,
                     CipherMode eMode,
                     CipherDigestType eDigestType,
                     CipherResultType eRTType)
{
    if (!is.good())
    {
        return;
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::string strDigestType = arrDigestType[eDigestType];
    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           strPassPhrase,
                                                                                                                           strSalt,
                                                                                                                           2000,
                                                                                                                           strDigestType));
    if (pCipher.get() == nullptr)
    {
        return;
    }

    Poco::Crypto::CryptoTransform::Ptr pDecryptor = pCipher->createDecryptor();
    Poco::Crypto::CryptoInputStream decryptorStream(is, pDecryptor);
    Poco::StreamCopier::copyStream(decryptorStream, os);
}

// Decrypt the stream
void BCrypto::DecryptStream(std::istream &is,
                     std::ostream &os,
                     std::string strKey,
                     std::string strIV,
                     BlockType eBlockType,
                     CipherMode eMode,
                     CipherResultType eRTType)
{
    if (!is.good())
    {
        return;
    }

    if (strKey.empty())
    {
        strKey = "wewqerrqwerqwrqffdsfrwerqgdgbsgr";
    }

    if (strIV.empty())
    {
        strIV = "dkfdjgakhfjuwenjkshfiwdplpnmfjqa";
    }

    std::string strKeyType = arrBlockType[eBlockType] + "-" + arrCipherMode[eMode];
    std::vector<unsigned char> vKey;
    for (size_t index = 0; index < strKey.length(); index++)
    {
        vKey.push_back(strKey[index]);
    }
    std::vector<unsigned char> vIV;
    for (size_t index = 0; index < strIV.length(); index++)
    {
        vIV.push_back(strIV[index]);
    }

    Poco::Crypto::Cipher::Ptr pCipher = Poco::Crypto::CipherFactory::defaultFactory().createCipher(Poco::Crypto::CipherKey(strKeyType,
                                                                                                                           vKey,
                                                                                                                           vIV));
    if (pCipher.get() == nullptr)
    {
        return;
    }

    Poco::Crypto::CryptoTransform::Ptr pDecryptor = pCipher->createDecryptor();
    Poco::Crypto::CryptoInputStream decryptorStream(is, pDecryptor);
    Poco::StreamCopier::copyStream(decryptorStream, os);
}
